#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
SmartUI 后端服务模块

该模块实现了SmartUI的Web后端服务，提供了用户认证、项目管理、任务管理、设备管理、报告管理等功能。
主要包含以下组件：
- 用户认证：登录、登出和会话管理
- WebSocket通信：与前端的实时通信
- RESTful API：提供各种资源的API接口
- 页面渲染：渲染各种页面模板

依赖：
- Flask: Web框架
- Flask-SocketIO: WebSocket支持
- Flask-CORS: 跨域资源共享支持

使用方法：
直接运行该文件启动服务器，可以通过命令行参数配置服务器行为。
例如：python backend.py --enable-mock
"""

from flask import Flask, render_template, jsonify, request, redirect, url_for
from flask_cors import CORS
from flask_socketio import SocketIO
from flask import flash
from functools import wraps
from datetime import timedelta
from flask import session
from utils import parse_command_args


# 初始化Flask应用
args = parse_command_args()
app = Flask(__name__)
CORS(app)  # 启用跨域请求支持
socketio = SocketIO(app, cors_allowed_origins="*", async_mode='eventlet')  # 初始化SocketIO
if args.enable_mock:
    from mock.control import Control
    app.config['HOST'] = '0.0.0.0'
    app.config['PORT'] = 5000
    app.config['DEBUG'] = True
    app.config['USE_RELOADER'] = True
    app.config['SECRET_KEY'] = 'default_secret_key'
else:
    from utils import init_app_config
    from control import Control
    init_app_config(app, args)
control = Control(app, session)


################################## WebSocket事件处理 ##################################
@socketio.on('connect')
def connect():
    """WebSocket连接事件处理函数
    
    当前端通过WebSocket连接到服务器时触发此事件。
    记录连接日志并调用控制器的connect方法处理连接。
    
    参数：
        无
    
    返回：
        无
    """
    app.logger.info('Frontend connected to WebSocket')
    control.connect(request)

@socketio.on('disconnect')
def disconnect():
    """WebSocket断开连接事件处理函数
    
    当前端与服务器的WebSocket连接断开时触发此事件。
    记录断开连接日志并调用控制器的disconnect方法处理断开连接。
    
    参数：
        无
    
    返回：
        无
    """
    app.logger.info('Frontend disconnected from WebSocket')
    control.disconnect(request)

#################################### 登录路由定义 ####################################
# 登录验证装饰器
def login_required(f):
    """登录验证装饰器
    
    用于保护需要登录才能访问的路由。如果用户未登录，将重定向到登录页面。
    
    参数：
        f: 被装饰的函数
        
    返回：
        decorated_function: 装饰后的函数
    """
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'username' not in session:
            app.logger.warning(f'未授权访问: {request.path}, IP: {request.remote_addr}')
            return redirect(url_for('login', next=request.url))
        app.logger.debug(f'用户 {session.get("username")} 访问受保护路由: {request.path}')
        return f(*args, **kwargs)
    return decorated_function

@app.route('/login', methods=['GET', 'POST'])
def login():
    """用户登录处理
    
    处理用户登录请求。GET请求返回登录页面，POST请求处理登录表单提交。
    
    请求方法：
        GET: 显示登录页面
        POST: 处理登录表单提交
        
    表单参数：
        username: 用户名
        password: 密码
        remember: 记住登录状态（可选）
        
    返回：
        GET: 登录页面
        POST成功: 重定向到首页或请求的下一页
        POST失败: 带有错误信息的登录页面
    """
    app.logger.debug('用户访问登录页面')
    error = None
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        remember = 'remember' in request.form
        app.logger.debug(f'用户 {username} 尝试登录，记住登录状态: {remember}')
        # 验证用户名和密码
        if control.login(username, password, remember):
            # 重定向到首页或请求的下一页
            next_page = request.args.get('next')
            app.logger.info(f'用户 {username} 登录成功')
            return redirect(next_page or url_for('index'))
        else:
            app.logger.warning(f'用户 {username} 登录失败：用户名或密码错误')
            error = '用户名或密码错误'
    return render_template('login.html', error=error)

@app.route('/logout')
def logout():
    """用户登出处理
    
    处理用户登出请求，清除会话并重定向到登录页面。
    
    请求方法：
        GET
        
    返回：
        重定向到登录页面
    """
    username = session.get('username', '未知用户')
    app.logger.info(f'用户 {username} 登出系统')
    # 清除会话
    control.logout()
    return redirect(url_for('login'))

#################################### 主页路由定义 ####################################
@app.route('/')
@login_required
def index():
    """首页
    
    显示应用程序的首页。需要用户登录。
    
    请求方法：
        GET
        
    返回：
        渲染后的index.html模板
    """
    app.logger.debug(f'用户 {session.get("username")} 访问首页')
    return render_template('index.html')

#################################### 项目路由定义 ####################################
@app.route('/project/<int:project_id>/<string:testcase>')
@login_required
def open_testcase(project_id, testcase):
    """打开测试用例页面

    根据项目ID和测试用例名称打开对应的测试用例步骤。如果测试用例不存在，显示错误消息并重定向到项目页面。

    请求方法：
        GET

    URL参数：
        project_id: 项目ID（整数）
        testcase: 测试用例名称（字符串）
    
    返回：
        成功: 流程编辑器中渲染测试用例步骤
        失败: 重定向到项目页面，并显示错误消息
    """
    app.logger.debug(f'用户 {session.get("username")} 尝试打开测试用例: {testcase}')
    response = control.call_project_api("open_testcase", {"project_id": project_id, "testcase": testcase})
    return jsonify(response['content']), response['code']
    
@app.route('/project/<int:project_id>')
@login_required
def open_project(project_id):
    """打开项目页面
    
    根据项目ID打开对应的项目页面。如果项目不存在，显示错误消息并重定向到首页。
    
    请求方法：
        GET
        
    URL参数：
        project_id: 项目ID（整数）
        
    返回：
        成功: 渲染后的project.html模板，包含项目数据
        失败: 重定向到首页，并显示错误消息
    """
    app.logger.debug(f'用户 {session.get("username")} 尝试打开项目 ID: {project_id}')
    response = control.call_project_api("open_project", {"id": project_id})
    if response['code'] >= 400:
        # 添加闪现消息告知用户
        flash(response['content']['message'], 'error')
        # 记录日志
        app.logger.warning(f'用户 {session.get("username")} 尝试打开不存在的项目 ID: {project_id}')
        return redirect(url_for('index'))
    project_data = response['content']
    app.logger.info(f'用户 {session.get("username")} 成功打开项目 ID: {project_id}, 项目名称: {project_data.get("name", "未知")}')
    return render_template('project.html', project=project_data)

@app.route('/api/projects', methods=['GET', 'POST'])
@login_required
def call_project_api():
    """项目API调用
    
    处理项目相关的API请求。
        
    查询参数：
        api: API名称
        其他参数: 根据具体API需要
        
    返回：
        JSON响应，包含API调用结果和状态码
    """
    if request.method == 'POST':
        args = request.json
        api = request.args.get('api')
    else:
        args = request.args.to_dict()
        api = args.pop('api')
    app.logger.debug(f'用户 {session.get("username")} 调用项目API: {api}, 参数: {args}')
    
    response = control.call_project_api(api, args)
    
    if response['code'] >= 400:
        app.logger.warning(f'项目API {api} 调用失败: {response["content"]}')
    else:
        app.logger.debug(f'项目API {api} 调用成功')
        
    return jsonify(response['content']), response['code']

#################################### 任务路由定义 ####################################
@app.route('/task/<int:task_id>')
@login_required
def open_task(task_id):
    """打开任务页面
    
    根据任务ID打开对应的任务页面。如果任务不存在，显示错误消息并重定向到首页。
    
    请求方法：
        GET
        
    URL参数：
        task_id: 任务ID（整数）
        
    返回：
        成功: 渲染后的task.html模板，包含任务数据
        失败: 重定向到首页，并显示错误消息
    """
    app.logger.debug(f'用户 {session.get("username")} 尝试打开任务 ID: {task_id}')
    task_data = control.call_task_api("open_task", {"id": task_id})
    if not task_data:
        # 添加闪现消息告知用户
        flash('您请求的任务不存在或已被删除', 'error')
        # 记录日志
        app.logger.warning(f'用户 {session.get("username")} 尝试打开不存在的任务 ID: {task_id}')
        return redirect(url_for('index'))
    app.logger.info(f'用户 {session.get("username")} 成功打开任务 ID: {task_id}, 任务名称: {task_data.get("name", "未知")}')
    return render_template('task.html', task=task_data)

@app.route('/api/tasks', methods=['GET', 'POST'])
@login_required
def call_task_api():
    """任务API调用
    
    处理任务相关的API请求。
    
    请求方法：
        GET
        
    查询参数：
        api: API名称
        其他参数: 根据具体API需要
        
    返回：
        JSON响应，包含API调用结果和状态码
    """
    if request.method == 'POST':
        args = request.json
        api = request.args.get('api')
    else:
        args = request.args.to_dict()
        api = args.pop('api')
    app.logger.debug(f'用户 {session.get("username")} 调用任务API: {api}, 参数: {args}')
    
    response = control.call_task_api(api, args)
    
    if response['code'] >= 400:
        app.logger.warning(f'任务API {api} 调用失败: {response["content"]}')
    else:
        app.logger.debug(f'任务API {api} 调用成功')
        
    return jsonify(response['content']), response['code']

#################################### 设备路由定义 ####################################
@app.route('/device/<string:device_sn>')
@login_required
def open_device(device_sn):
    """打开设备页面
    
    根据设备序列号打开对应的设备页面。如果设备不存在，显示错误消息并重定向到首页。
    
    请求方法：
        GET
        
    URL参数：
        device_sn: 设备序列号（字符串）
        
    返回：
        成功: 渲染后的device.html模板，包含设备数据
        失败: 重定向到首页，并显示错误消息
    """
    app.logger.debug(f'用户 {session.get("username")} 尝试打开设备 SN: {device_sn}')
    device_data = control.call_device_api("open_device", {"sn": device_sn})
    if not device_data:
        # 添加闪现消息告知用户
        flash('您请求的设备不存在或已被删除', 'error')
        # 记录日志
        app.logger.warning(f'用户 {session.get("username")} 尝试打开不存在的设备 SN: {device_sn}')
        return redirect(url_for('index'))
    app.logger.info(f'用户 {session.get("username")} 成功打开设备 SN: {device_sn}, 设备名称: {device_data.get("name", "未知")}')
    return render_template('device.html', device=device_data)

@app.route('/api/devices', methods=['GET', 'POST'])
@login_required
def call_device_api():
    """设备API调用
    
    处理设备相关的API请求。
    
    请求方法：
        GET
        
    查询参数：
        api: API名称
        其他参数: 根据具体API需要
        
    返回：
        JSON响应，包含API调用结果和状态码
    """
    if request.method == 'POST':
        args = request.json
        api = request.args.get('api')
    else:
        args = request.args.to_dict()
        api = args.pop('api')
    app.logger.debug(f'用户 {session.get("username")} 调用设备API: {api}, 参数: {args}')
    
    response = control.call_device_api(api, args)
    
    if response['code'] >= 400:
        app.logger.warning(f'设备API {api} 调用失败: {response["content"]}')
    else:
        app.logger.debug(f'设备API {api} 调用成功')
        
    return jsonify(response['content']), response['code']

#################################### 报告路由定义 ####################################
@app.route('/report/<int:report_id>')
@login_required
def open_report(report_id):
    """打开报告页面
    
    根据报告ID打开对应的报告页面。如果报告不存在，显示错误消息并重定向到首页。
    
    请求方法：
        GET
        
    URL参数：
        report_id: 报告ID（整数）
        
    返回：
        成功: 渲染后的report.html模板，包含报告数据
        失败: 重定向到首页，并显示错误消息
    """
    app.logger.debug(f'用户 {session.get("username")} 尝试打开报告 ID: {report_id}')
    report_data = control.call_report_api("open_report", {"id": report_id})
    if not report_data:
        # 添加闪现消息告知用户
        flash('您请求的报告不存在或已被删除', 'error')
        # 记录日志
        app.logger.warning(f'用户 {session.get("username")} 尝试打开不存在的报告 ID: {report_id}')
        return redirect(url_for('index'))
    app.logger.info(f'用户 {session.get("username")} 成功打开报告 ID: {report_id}, 报告名称: {report_data.get("name", "未知")}')
    return render_template('report.html', report=report_data)

@app.route('/api/reports', methods=['GET'])
@login_required
def call_report_api():
    """报告API调用
    
    处理报告相关的API请求。
    
    请求方法：
        GET
        
    查询参数：
        api: API名称
        其他参数: 根据具体API需要
        
    返回：
        JSON响应，包含API调用结果和状态码
    """
    if request.method == 'POST':
        args = request.json
        api = request.args.get('api')
    else:
        args = request.args.to_dict()
        api = args.pop('api')
    app.logger.debug(f'用户 {session.get("username")} 调用报告API: {api}, 参数: {args}')
    
    response = control.call_report_api(api, args)
    
    if response['code'] >= 400:
        app.logger.warning(f'报告API {api} 调用失败: {response["content"]}')
    else:
        app.logger.debug(f'报告API {api} 调用成功')
        
    return jsonify(response['content']), response['code']

#################################### 模板路由定义 ####################################
@app.route('/api/templates', methods=['GET'])
@login_required
def call_template_api():
    """模板API调用
    
    处理模板相关的API请求。
    
    请求方法：
        GET
        
    查询参数：
        api: API名称
        其他参数: 根据具体API需要
        
    返回：
        JSON响应，包含API调用结果和状态码
    """
    if request.method == 'POST':
        args = request.json
        api = request.args.get('api')
    else:
        args = request.args.to_dict()
        api = args.pop('api')
    app.logger.debug(f'用户 {session.get("username")} 调用模板API: {api}, 参数: {args}')
    
    response = control.call_template_api(api, args)
    
    if response['code'] >= 400:
        app.logger.warning(f'模板API {api} 调用失败: {response["content"]}')
    else:
        app.logger.debug(f'模板API {api} 调用成功')
        
    return jsonify(response['content']), response['code']

#################################### 配置路由定义 ####################################
@app.route('/api/configs', methods=['GET', 'POST'])
@login_required
def call_config_api():
    """配置API调用
    
    处理配置相关的API请求。
    
    请求方法：
        GET
        
    查询参数：
        api: API名称
        其他参数: 根据具体API需要
        
    返回：
        JSON响应，包含API调用结果和状态码
    """
    if request.method == 'POST':
        args = request.json
        api = request.args.get('api')
    else:
        args = request.args.to_dict()
        api = args.pop('api')
    app.logger.debug(f'用户 {session.get("username")} 调用配置API: {api}, 参数: {args}')
    
    response = control.call_config_api(api, args)
    
    if response['code'] >= 400:
        app.logger.warning(f'配置API {api} 调用失败: {response["content"]}')
    else:
        app.logger.debug(f'配置API {api} 调用成功')
        
    return jsonify(response['content']), response['code']


if __name__ == '__main__':
    """主程序入口
    
    启动Flask应用服务器，使用SocketIO提供WebSocket支持。
    服务器配置（主机、端口、调试模式等）从应用配置中获取。
    当服务器关闭时，确保正确关闭数据库连接和代理客户端连接。
    
    使用方法：
        python backend.py [参数]
        
    参数示例：
        --enable-mock: 启用模拟模式
        --host: 指定主机地址
        --port: 指定端口号
    """
    try:
        app.logger.info(f'正在启动SmartUI后端服务，主机: {app.config["HOST"]}, 端口: {app.config["PORT"]}')
        # Run the Flask app with SocketIO
        socketio.run(
                app,
                host=app.config['HOST'],
                port=app.config['PORT'],
                debug=app.config['DEBUG'],
                use_reloader=app.config['USE_RELOADER'])
    finally:
        control.destroy()    
        app.logger.info('SmartUI后端服务已停止')